/* Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.activiti.engine.impl.variable;

import jakarta.persistence.EntityManager;
import jakarta.persistence.EntityManagerFactory;
import jakarta.persistence.PersistenceException;
import jakarta.persistence.TransactionRequiredException;

import org.activiti.engine.ActivitiException;
import org.activiti.engine.impl.cfg.TransactionContext;
import org.activiti.engine.impl.cfg.TransactionListener;
import org.activiti.engine.impl.cfg.TransactionState;
import org.activiti.engine.impl.context.Context;
import org.activiti.engine.impl.interceptor.CommandContext;

/**
 * @author Frederik Heremans
 * @author Joram Barrez
 */
public class EntityManagerSessionImpl implements EntityManagerSession {

    private EntityManagerFactory entityManagerFactory;
    private EntityManager entityManager;
    private boolean handleTransactions;
    private boolean closeEntityManager;

    public EntityManagerSessionImpl(EntityManagerFactory entityManagerFactory, EntityManager entityManager,
            boolean handleTransactions, boolean closeEntityManager) {
        this(entityManagerFactory, handleTransactions, closeEntityManager);
        this.entityManager = entityManager;
    }

    public EntityManagerSessionImpl(EntityManagerFactory entityManagerFactory, boolean handleTransactions, boolean closeEntityManager) {
        this.entityManagerFactory = entityManagerFactory;
        this.handleTransactions = handleTransactions;
        this.closeEntityManager = closeEntityManager;
    }

    @Override
    public void flush() {
        if (entityManager != null && (!handleTransactions || isTransactionActive())) {
            try {
                entityManager.flush();
            } catch (IllegalStateException ise) {
                throw new ActivitiException("Error while flushing EntityManager, illegal state", ise);
            } catch (TransactionRequiredException tre) {
                throw new ActivitiException("Cannot flush EntityManager, an active transaction is required", tre);
            } catch (PersistenceException pe) {
                throw new ActivitiException("Error while flushing EntityManager: " + pe.getMessage(), pe);
            }
        }
    }

    protected boolean isTransactionActive() {
        if (handleTransactions && entityManager.getTransaction() != null) {
            return entityManager.getTransaction().isActive();
        }
        return false;
    }

    @Override
    public void close() {
        if (closeEntityManager && entityManager != null && !entityManager.isOpen()) {
            try {
                entityManager.close();
            } catch (IllegalStateException ise) {
                throw new ActivitiException("Error while closing EntityManager, may have already been closed or it is container-managed", ise);
            }
        }
    }

    @Override
    public EntityManager getEntityManager() {
        if (entityManager == null) {
            entityManager = getEntityManagerFactory().createEntityManager();

            if (handleTransactions) {
                // Add transaction listeners, if transactions should be handled
                TransactionListener jpaTransactionCommitListener = new TransactionListener() {
                    @Override
                    public void execute(CommandContext commandContext) {
                        if (isTransactionActive()) {
                            entityManager.getTransaction().commit();
                        }
                    }
                };

                TransactionListener jpaTransactionRollbackListener = new TransactionListener() {
                    @Override
                    public void execute(CommandContext commandContext) {
                        if (isTransactionActive()) {
                            entityManager.getTransaction().rollback();
                        }
                    }
                };

                TransactionContext transactionContext = Context.getCommandContext().getTransactionContext();
                transactionContext.addTransactionListener(TransactionState.COMMITTED, jpaTransactionCommitListener);
                transactionContext.addTransactionListener(TransactionState.ROLLED_BACK, jpaTransactionRollbackListener);

                // Also, start a transaction, if one isn't started already
                if (!isTransactionActive()) {
                    entityManager.getTransaction().begin();
                }
            }
        }

        return entityManager;
    }

    private EntityManagerFactory getEntityManagerFactory() {
        return entityManagerFactory;
    }
}
